package extraction;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import minhash.LocalitySentitiveHashing;
import utils.Pair;

public class TestClassification {

  private static int knn = 5;
  private static int signature = 400;	
  private static int bands = 50;
  private static boolean separateDirection = false;
   
  private static int trainInstances = 0;
  private static int testInstances = 0;
  
  private static LocalitySentitiveHashing dataIndex;  
  private static LocalitySentitiveHashing directionIndex;
  
  public static void readGoldenStandard( String file) throws IOException {
	  BufferedReader input = new BufferedReader(new FileReader(file));
	  String aux = null;
	  String sentence = null;	  
	  String e1 = null;
	  String e2 = null;	  
	  String type = null;
	  
	  Set<String> goldenStandard = new HashSet<String>();
	  
	  while ((aux = input.readLine()) != null) {
		  if (aux.startsWith("SENTENCE")) {
			  sentence = aux.split(": ")[1];
			  aux = input.readLine();
			  if (aux.equals("")) aux = input.readLine();
			  if (aux.startsWith("MANUALLY CHECKED : IGNORE")) continue;
			  if (aux.startsWith("MANUALLY CHECKED : TRUE")) {
				  while (!aux.startsWith("*")) {
					  if (aux.startsWith("ENTITY1")) e1 = aux.split(": ")[1].trim();
					  if (aux.startsWith("ENTITY2")) e2 = aux.split(": ")[1].trim();
					  if (aux.startsWith("REL TYPE")) type = aux.split(": ")[1];
					  aux = input.readLine();
				  }
			  }
			  StringBuffer i = new StringBuffer(); 			  
			  i.append(sentence);
			  i.append(";");
			  i.append(e1);
			  i.append(";");				
			  i.append(e2);				
			  i.append(";");
			  i.append(type);
			  goldenStandard.add(i.toString());			  
		  }
	  }
	  input.close();
  }
  
  public static void readTrainData ( String file ) throws Exception {
    readTrainData(file,-1);
  }
  		
  public static void readTrainData ( String file, int number ) throws Exception {
    dataIndex = new LocalitySentitiveHashing(signature , bands );
    directionIndex = new LocalitySentitiveHashing(signature , bands );      
    LineNumberReader  lnr = new LineNumberReader(new FileReader(new File(file)));
    lnr.skip(Long.MAX_VALUE);
    int num_lines = lnr.getLineNumber();
    BufferedReader input = new BufferedReader( new FileReader(file) );
    String aux = null;
	int num=0;	
	long startTime = System.nanoTime();
    while ( ( aux = input.readLine() ) != null ) {
      if ( num % 1000 == 0 ) System.out.println(String.valueOf(num) + "/" + String.valueOf(num_lines));      
      HashSet<String> set = new HashSet<String>();
      for ( String element : aux.substring(aux.indexOf(" ")+1).trim().split(" ") ) set.add(element);
      String cl = aux.substring(0,aux.indexOf(" "));
      if ( cl.indexOf("(") != -1 && separateDirection ) {
    	  String dr = cl.substring(cl.indexOf("("));
    	  cl = cl.substring(0,cl.indexOf("("));
          directionIndex.index(directionIndex.indexSize(),set.toArray(new String[0]),dr);
      }
      dataIndex.index(dataIndex.indexSize(),set.toArray(new String[0]),cl);      
      num++;
      if ( number > 0 && num > number) break;
      if ( num % 10000 == 0 ) dataIndex.commitChanges();
    }  
	long stopTime = System.nanoTime();
	long elapsedTime = stopTime - startTime;
	System.out.println("Indexing time: " + TimeUnit.SECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS));
    dataIndex.commitChanges();
    //dataIndex.computeValidity(knn);
    input.close();
  }
  
  public static LinkedList<Pair<String, String>> evaluateTestData ( String file ) throws Exception { 
    return evaluateTestData(file,-1); 
  }
  		
  public static LinkedList<Pair<String,String>> evaluateTestData ( String file, int number ) throws Exception {
	if ( number > 0 ) readTrainData(file,number);
    BufferedReader input = new BufferedReader( new FileReader(file) );
    String aux = null;    
	LinkedList<Pair<String,String>> results = new LinkedList<Pair<String,String>>();
	long startTime = System.nanoTime();
    while ( ( aux = input.readLine() ) != null ) {
	  if ( number-- > 0) continue;
      HashSet<String> set = new HashSet<String>();
      for ( String element : aux.substring(aux.indexOf(" ")+1).trim().split(" ") ) set.add(element);
      String cl = aux.substring(0,aux.indexOf(" "));      
      String clResult = dataIndex.queryNearest(set.toArray(new String[0]),knn, null).mostFrequent();      
      if ( clResult!= null && separateDirection) clResult += directionIndex.queryNearest(set.toArray(new String[0]),knn).mostFrequent();
      Pair<String,String> p = new Pair<String, String>(cl, clResult);
      results.add(p);      
    }
    long stopTime = System.nanoTime();
    long elapsedTime = stopTime - startTime;
    System.out.println("Classification time: " + TimeUnit.SECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS));
    input.close();
	return results;
  }

  public static double[] evaluateResults(LinkedList<Pair<String,String>> results, String class_relation){
	  
	  double numInstancesOfClass = 0;
	  double numCorrectClassified = 0;
	  double numClassified = 0;	  
	  
	  for (Pair<String, String> pair : results) {		  
		  if (pair.getSecond() == null) pair.setSecond("UNKNOWN");	
		  String first = pair.getFirst();
		  String second = pair.getSecond();
		  if (first.equalsIgnoreCase(class_relation)) {
			  numInstancesOfClass++;
			  if (first.equalsIgnoreCase(second)) {
				  numCorrectClassified++;
			  }
			  else if (!first.equalsIgnoreCase(second));
		  }		  		  
		  if (second.equalsIgnoreCase(class_relation)) numClassified++;
	  }
	  
	  double precision = numClassified == 0 ? 1.0 : (numCorrectClassified / numClassified);
	  double recall = numInstancesOfClass == 0 ? 1.0 : (numCorrectClassified / numInstancesOfClass);
	  double f1 = precision == 0 && recall == 0 ? 0.0 : (2.0*((precision*recall)/(precision+recall)));	  
	  
	  System.out.println("Results for class \t" + class_relation + "\t" + (dataIndex.indexSize(class_relation) + (int) numInstancesOfClass));
	  System.out.println("Number of training instances : " + dataIndex.indexSize( class_relation ) );
	  System.out.println("Number of test instances : " + numInstancesOfClass );
	  System.out.println("Number of classifications : " + numClassified );
	  System.out.println("Precision : " + precision );
	  System.out.println("Recall : " + recall );
	  System.out.println("F1 : " + f1 );
	  System.out.println();
	  
	  trainInstances += dataIndex.indexSize( class_relation );
	  testInstances += numInstancesOfClass;
	  
	  double accuracy = numCorrectClassified / (float) results.size(); 
	  
	  return new double[]{ accuracy, precision, recall, f1 };
  }
   
  public static void testWikiPT(String[] args) throws Exception { 
	  
	  signature = Integer.parseInt(args[1]);
	  bands = Integer.parseInt(args[2]);
	  knn = Integer.parseInt(args[3]);	  
	  System.out.println("min-hash: " + signature);
	  System.out.println("LSH: " + bands);
	  System.out.println("kNN: " + knn);
	  System.out.println("Reading train data WikiPT...");
	  readTrainData("train-data-wikipt.txt");
      System.out.println("Reading test data WikiPT...");
      LinkedList<Pair<String,String>> all_results = evaluateTestData("test-data-wikipt.txt");	      
      double[] results = { 0.0, 0.0, 0.0, 0.0 };
      
      String[] classes_simmetrycal = {"locatedInArea","origin","partOf","deathOrBurialPlace","successor","keyPerson","parent","influencedBy","partner","other"};
      
      String[] classes_asymmetrical = {"locatedInArea(e1,e2)","locatedInArea(e2,e1)",
    		  							"origin(e1,e2)","origin(e2,e1)",    		  							
    		  							"partOf(e1,e2)","partOf(e2,e1)",
    		  							"deathOrBurialPlace(e1,e2)","deathOrBurialPlace(e2,e1)",
    		  							"successor(e1,e2)","successor(e2,e1)",
    		  							"keyPerson(e1,e2)","keyPerson(e2,e1)",
    		  							"parent(e1,e2)","parent(e2,e1)",
    		  							"influencedBy(e1,e2)","influencedBy(e2,e1)",
    		  							"partner","other"};
      String[] classes = null;
      
      if (Boolean.parseBoolean((args[4]))) classes = classes_simmetrycal; 
      else classes = classes_asymmetrical;
      
      for ( String c : classes  ) {	    	  
    	  double[] results_aux = evaluateResults(all_results,c);		  
    	  for ( int j = 1; j < results_aux.length; j++) results[j] = results[j] + results_aux[j];
    	  results[0] = results_aux[0];
      }
      
      for (int i = 1; i < results.length; i++) results[i] = results[i] / classes.length;
      
      System.out.println();
      System.out.println("Total train instances : " + trainInstances);
      System.out.println("Total test instances : " + testInstances);
      System.out.println("Macro-Average results for all classes...");	
      System.out.println("Accuracy : " + results[0] );
  	  System.out.println("Precision : " + results[1]);
  	  System.out.println("Recall : " + results[2]);
  	  System.out.println("F1: " + (2.0*((results[1]*results[2])/(results[1]+results[2]))) );
  	  System.out.println();
	 }
  }